/*
 * 纯C实现的Redis客户端，支持基本的get, set, ttl, expire, hset, sadd, scard, lpush, lpop等功能
 * 不依赖hiredis，仅使用socket通信
 * 优化：连接失败时详细报错，支持IP和主机名，连接后主动PING测试，超时设置，连接关闭时清理
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/time.h>

#define REDIS_DEFAULT_PORT 6379
#define REDIS_DEFAULT_HOST "127.0.0.1"
#define REDIS_BUF_SIZE 4096

typedef struct {
    int sockfd;
    char host[128];
    int port;
    char buf[REDIS_BUF_SIZE];
} redisClient;

// 前置声明
int redis_command(redisClient *c, char *reply, size_t replylen, int argc, ...);

// 设置socket超时
static int set_socket_timeout(int sockfd, int sec) {
    struct timeval tv;
    tv.tv_sec = sec;
    tv.tv_usec = 0;
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
        perror("setsockopt RCVTIMEO");
        return -1;
    }
    if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
        perror("setsockopt SNDTIMEO");
        return -1;
    }
    return 0;
}

// 连接Redis服务器，支持IP和主机名，连接后主动PING测试
int redis_connect(redisClient *c, const char *host, int port) {
    struct sockaddr_in serv_addr;
    struct hostent *server = NULL;
    int is_ip = 0;

    // 判断host是否为IP
    struct in_addr addr;
    if (inet_aton(host, &addr)) {
        is_ip = 1;
    }

    c->sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (c->sockfd < 0) {
        perror("ERROR opening socket");
        return -1;
    }

    memset((char *)&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);

    if (is_ip) {
        serv_addr.sin_addr = addr;
    } else {
        server = gethostbyname(host);
        if (server == NULL) {
            fprintf(stderr, "ERROR, no such host: %s\n", host);
            close(c->sockfd);
            c->sockfd = -1;
            return -1;
        }
        memcpy((char *)&serv_addr.sin_addr.s_addr, (char *)server->h_addr, server->h_length);
    }

    // 设置连接超时
    set_socket_timeout(c->sockfd, 3);

    if (connect(c->sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        fprintf(stderr, "ERROR connecting to %s:%d: %s\n", host, port, strerror(errno));
        close(c->sockfd);
        c->sockfd = -1;
        return -1;
    }
    strncpy(c->host, host, sizeof(c->host)-1);
    c->host[sizeof(c->host)-1] = '\0';
    c->port = port;

    // 连接后主动PING测试
    char reply[256];
    if (redis_command(c, reply, sizeof(reply), 1, "PING") != 0) {
        fprintf(stderr, "ERROR: Redis PING failed, not a redis server or connection broken\n");
        close(c->sockfd);
        c->sockfd = -1;
        return -1;
    }
    if (strncmp(reply, "+PONG", 5) != 0) {
        fprintf(stderr, "ERROR: Redis PING reply unexpected: %s\n", reply);
        close(c->sockfd);
        c->sockfd = -1;
        return -1;
    }
    return 0;
}

// 关闭连接
void redis_close(redisClient *c) {
    if (c->sockfd >= 0) {
        close(c->sockfd);
        c->sockfd = -1;
    }
}

// 发送命令
int redis_send(redisClient *c, const char *cmd, size_t len) {
    size_t sent = 0;
    while (sent < len) {
        ssize_t n = send(c->sockfd, cmd + sent, len - sent, 0);
        if (n < 0) {
            perror("send");
            return -1;
        }
        if (n == 0) break;
        sent += n;
    }
    return sent == len ? 0 : -1;
}

// 读取一行
int redis_readline(redisClient *c, char *line, size_t maxlen) {
    size_t n = 0;
    char ch;
    while (n < maxlen - 1) {
        ssize_t r = recv(c->sockfd, &ch, 1, 0);
        if (r < 0) {
            if (errno == EINTR) continue;
            perror("recv");
            break;
        }
        if (r == 0) break;
        if (ch == '\r') continue;
        if (ch == '\n') break;
        line[n++] = ch;
    }
    line[n] = '\0';
    return n;
}

// 读取bulk字符串
int redis_readbulk(redisClient *c, char *buf, size_t maxlen) {
    char line[64];
    if (redis_readline(c, line, sizeof(line)) <= 0) return -1;
    if (line[0] != '$') return -1;
    int len = atoi(line+1);
    if (len < 0 || len >= (int)maxlen) return -1;
    size_t readed = 0;
    while (readed < (size_t)len) {
        ssize_t r = recv(c->sockfd, buf + readed, len - readed, 0);
        if (r <= 0) return -1;
        readed += r;
    }
    buf[len] = '\0';
    // 读掉\r\n
    recv(c->sockfd, line, 2, 0);
    return len;
}

// 发送RESP格式命令
int redis_command(redisClient *c, char *reply, size_t replylen, int argc, ...) {
    char cmd[REDIS_BUF_SIZE];
    size_t pos = 0;
    va_list ap;
    va_start(ap, argc);
    pos += snprintf(cmd+pos, sizeof(cmd)-pos, "*%d\r\n", argc);
    for (int i = 0; i < argc; ++i) {
        const char *arg = va_arg(ap, const char *);
        int arglen = strlen(arg);
        pos += snprintf(cmd+pos, sizeof(cmd)-pos, "$%d\r\n", arglen);
        if (pos + arglen + 2 > sizeof(cmd)) {
            va_end(ap);
            return -1;
        }
        memcpy(cmd+pos, arg, arglen);
        pos += arglen;
        cmd[pos++] = '\r';
        cmd[pos++] = '\n';
    }
    va_end(ap);
    if (redis_send(c, cmd, pos) != 0) return -1;
    if (reply && replylen > 0) {
        // 读取一行
        if (redis_readline(c, reply, replylen) <= 0) return -1;
    }
    return 0;
}

// GET
int redis_get(redisClient *c, const char *key, char *value, size_t vlen) {
    char line[REDIS_BUF_SIZE];
    int cmd_ret = redis_command(c, line, sizeof(line), 2, "GET", key);
    printf("redis_command return: %d, reply: [%s]\n", cmd_ret, line);
    if (cmd_ret != 0) {
        value[0] = '\0';
        return -1;
    }


    

    printf("redis_get: line = [%s]\n", line);
    if (line[0] == '$') {
        int len = atoi(line+1);
        if (len < 0) {
            value[0] = '\0';
            return 0; // key不存在，不算错误
        }
        int ret = -1;
        int retry = 0;
        while (retry < 3) {
            ret = redis_readbulk(c, value, vlen);
            if (ret >= 0) return ret;
            // 检查errno是否为EAGAIN或EWOULDBLOCK，遇到临时不可用时重试
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                fprintf(stderr, "redis_get: Resource temporarily unavailable, retrying (%d)...\n", retry+1);
                usleep(100 * 1000); // 100ms
                retry++;
                continue;
            } else {
                break;
            }
        }
        value[0] = '\0';
        return -1;
    } else if (line[0] == '-') {
        // Redis返回错误，比如"-ERR ...", 记录错误信息
        fprintf(stderr, "Redis GET error: %s\n", line);
        value[0] = '\0';
        return -1;
    } else if (line[0] == '+') {
        // 有些Redis实现可能直接返回+OK或+nil
        if (strncmp(line, "+nil", 4) == 0 || strncmp(line, "+NULL", 5) == 0) {
            value[0] = '\0';
            return 0;
        }
        // 直接返回的字符串
        strncpy(value, line+1, vlen-1);
        value[vlen-1] = '\0';
        return strlen(value);
    }
    // 其他未知返回
    value[0] = '\0';
    return -1;
}

// SET
int redis_set(redisClient *c, const char *key, const char *value) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 3, "SET", key, value) != 0) return -1;
    if (strncmp(line, "+OK", 3) == 0) return 0;
    return -1;
}

// TTL
int redis_ttl(redisClient *c, const char *key) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 2, "TTL", key) != 0) return -1;
    if (line[0] == ':') {
        return atoi(line+1);
    }
    // 优化: key不存在时返回-2，过期时返回-1
    if (strncmp(line, "$-1", 3) == 0 || strncmp(line, ":-2", 3) == 0) {
        return -2;
    }
    return -1;
}

// EXPIRE
int redis_expire(redisClient *c, const char *key, int seconds) {
    char line[REDIS_BUF_SIZE], secstr[16];
    snprintf(secstr, sizeof(secstr), "%d", seconds);
    if (redis_command(c, line, sizeof(line), 3, "EXPIRE", key, secstr) != 0) return -1;
    if (line[0] == ':') return atoi(line+1);
    return -1;
}

// HSET
int redis_hset(redisClient *c, const char *key, const char *field, const char *value) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 4, "HSET", key, field, value) != 0) return -1;
    if (line[0] == ':') return atoi(line+1);
    return -1;
}

// HGET
int redis_hget(redisClient *c, const char *key, const char *field, char *value, size_t vlen) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 3, "HGET", key, field) != 0) return -1;
    if (line[0] == '$') {
        int len = atoi(line+1);
        if (len < 0) {
            value[0] = '\0';
            return 0; // not found, but not error
        }
        int ret = redis_readbulk(c, value, vlen);
        if (ret >= 0) return ret;
        value[0] = '\0';
        return -1;
    }
    value[0] = '\0';
    return -1;
}

// SADD
int redis_sadd(redisClient *c, const char *key, const char *member) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 3, "SADD", key, member) != 0) return -1;
    if (line[0] == ':') return atoi(line+1);
    return -1;
}

// SCARD
int redis_scard(redisClient *c, const char *key) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 2, "SCARD", key) != 0) return -1;
    if (line[0] == ':') return atoi(line+1);
    // 优化: key不存在时返回0
    if (strncmp(line, "$-1", 3) == 0) return 0;
    return -1;
}

// LPUSH
int redis_lpush(redisClient *c, const char *key, const char *value) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 3, "LPUSH", key, value) != 0) return -1;
    if (line[0] == ':') return atoi(line+1);
    return -1;
}

// LPOP
int redis_lpop(redisClient *c, const char *key, char *value, size_t vlen) {
    char line[REDIS_BUF_SIZE];
    if (redis_command(c, line, sizeof(line), 2, "LPOP", key) != 0) return -1;
    if (line[0] == '$') {
        int len = atoi(line+1);
        if (len < 0) {
            value[0] = '\0';
            return 0; // not found, but not error
        }
        int ret = redis_readbulk(c, value, vlen);
        if (ret >= 0) return ret;
        value[0] = '\0';
        return -1;
    }
    value[0] = '\0';
    return -1;
}

/* 用法示例*/
int main() {
    redisClient c;
    memset(&c, 0, sizeof(c));
    c.sockfd = -1;
    printf("Connecting to redis at %s:%d ...\n", REDIS_DEFAULT_HOST, REDIS_DEFAULT_PORT);
    if (redis_connect(&c, REDIS_DEFAULT_HOST, REDIS_DEFAULT_PORT) != 0) {
        printf("connect fail\n");
        return 1;
    }
    printf("Connected to redis.\n");

    if (redis_set(&c, "foo", "bar") == 0) {
        printf("SET foo bar OK\n");
    } else {
        printf("SET foo bar FAIL\n");
    }

    char val[128];
    int ret = redis_get(&c, "foo", val, sizeof(val));
    if (ret >= 0) {
        if (ret == 0) {
            printf("foo not found\n");
        } else {
            printf("foo=%s\n", val);
        }
    } else {
        printf("GET foo error\n");
    }

    // 测试 SET/GET
    if (redis_set(&c, "testkey", "12345") == 0) {
        printf("SET testkey 12345 OK\n");
    } else {
        printf("SET testkey FAIL\n");
    }
    memset(val, 0, sizeof(val));
    ret = redis_get(&c, "testkey", val, sizeof(val));
    if (ret > 0) {
        printf("GET testkey = %s\n", val);
    } else if (ret == 0) {
        printf("testkey not found\n");
    } else {
        printf("GET testkey error\n");
    }

    // 测试 EXPIRE/TTL
    if (redis_expire(&c, "testkey", 10) == 1) {
        printf("EXPIRE testkey 10s OK\n");
    } else {
        printf("EXPIRE testkey FAIL\n");
    }
    int ttl = redis_ttl(&c, "testkey");
    if (ttl >= 0) {
        printf("TTL testkey = %d\n", ttl);
    } else if (ttl == -2) {
        printf("TTL testkey: key not found\n");
    } else {
        printf("TTL testkey error\n");
    }

    // 测试 HSET/HGET
    if (redis_hset(&c, "myhash", "field1", "val1") >= 0) {
        printf("HSET myhash field1 val1 OK\n");
    } else {
        printf("HSET myhash FAIL\n");
    }
    memset(val, 0, sizeof(val));
    ret = redis_hget(&c, "myhash", "field1", val, sizeof(val));
    if (ret > 0) {
        printf("HGET myhash field1 = %s\n", val);
    } else if (ret == 0) {
        printf("myhash.field1 not found\n");
    } else {
        printf("HGET myhash error\n");
    }

    // 测试 SADD/SCARD
    if (redis_sadd(&c, "myset", "a") > 0) {
        printf("SADD myset a OK\n");
    }
    if (redis_sadd(&c, "myset", "b") > 0) {
        printf("SADD myset b OK\n");
    }
    int scard = redis_scard(&c, "myset");
    if (scard >= 0) {
        printf("SCARD myset = %d\n", scard);
    } else {
        printf("SCARD myset error\n");
    }

    // 测试 LPUSH/LPOP
    if (redis_lpush(&c, "mylist", "item1") >= 0) {
        printf("LPUSH mylist item1 OK\n");
    }
    if (redis_lpush(&c, "mylist", "item2") >= 0) {
        printf("LPUSH mylist item2 OK\n");
    }
    memset(val, 0, sizeof(val));
    ret = redis_lpop(&c, "mylist", val, sizeof(val));
    if (ret > 0) {
        printf("LPOP mylist = %s\n", val);
    } else if (ret == 0) {
        printf("LPOP mylist not found\n");
    } else {
        printf("LPOP mylist error\n");
    }

    redis_close(&c);
    return 0;
}
